package com.lanyeo.liver.task

import android.content.Context
import android.content.res.Configuration
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Point
import com.googlecode.tesseract.android.TessBaseAPI
import com.lanyeo.liver.R
import com.lanyeo.liver.ui.SettingResource
import com.lanyeo.liver.util.Engine
import com.lanyeo.liver.util.Object
import com.lanyeo.liver.util.TouchAction
import org.opencv.android.Utils
import org.opencv.core.Core
import org.opencv.core.CvType
import org.opencv.core.Mat
import org.opencv.core.MatOfPoint
import org.opencv.core.Rect
import org.opencv.imgproc.Imgproc
import java.io.ByteArrayOutputStream
import java.net.DatagramPacket
import java.net.DatagramSocket
import java.net.InetAddress
import java.util.concurrent.SynchronousQueue
import kotlin.math.pow
import kotlin.math.sqrt
import kotlin.random.Random

class TaskResource(override val context: Context) : ITask(context) {
    private var screenCenter = Point()
    private var screenSize = Point()
    private var actions = mutableSetOf<String>()
    private var imgHunger = Mat()
    private var imgOverload = Mat()
    private var imgAct = Mat()

    private var needJump = false
    private var preDistance = 0
    private var tryRotateScreen = 0
    private var currentUnloadStatus = UnloadStatus.FINISH
    private var trySearchFailCount = 0
    private var checkHungerNextTime = 0L
    private var checkOverloadNextTime = 0L
    private var checkCanDoNextTime = 0L
    private var checkSearchNextTime = 0L
    private var adjustViewNextTime = 0L

    // 活动弹窗
    private var checkActPopupNextTime = 0L
    private var needSearch = false

    // 是否快速移动
    private var needFastMove = false
    // 是否开启Debug模式
    private var isOpenDebug = false
    private var workThreadRunning = false

    override fun initTask(onTaskTouchListener: OnTaskTouchListener?, taskParams: Any): Boolean {
        actions.clear()
        actions.addAll(taskParams as Set<String>)
        needFastMove = actions.contains(SettingResource.TAG_FAST_MOVE)
        isOpenDebug = actions.contains(SettingResource.TAG_DEBUG)

        val bundlePath = context.applicationContext.getExternalFilesDir("bundle")!!.absolutePath
        Utils.bitmapToMat(BitmapFactory.decodeFile("$bundlePath/common/hunger.jpg"), imgHunger)
        imgHunger.convertTo(imgHunger, CvType.CV_32F)

        val overloadImage = Mat()
        Utils.bitmapToMat(BitmapFactory.decodeFile("$bundlePath/resource/overload.jpg"), overloadImage)
        Imgproc.threshold(overloadImage, imgOverload, 130.0, 255.0, Imgproc.THRESH_BINARY)

        Utils.bitmapToMat(BitmapFactory.decodeFile("$bundlePath/common/act.jpg"), imgAct)
        imgAct.convertTo(imgAct, CvType.CV_32F)

        if (!workThreadRunning) {
            UDPClient.start()
            workThreadRunning = true
        }

        return super.initTask(onTaskTouchListener, taskParams)
    }

    override fun doTask(screen: Bitmap, screenRotation: Int): Long {
        if (screenRotation != Configuration.ORIENTATION_LANDSCAPE) {
            return -1
        }
        screenSize.x = screen.width
        screenSize.y = screen.height
        screenCenter.x = screenSize.x / 2
        screenCenter.y = screenSize.y / 2
        setIsRunning(true)

        // 检测是否可以做采集等动作
        if (checkCanDo(screen)) {
            trySearchFailCount = 0
            needSearch = true
            return nextTime(1)
        }

        if (needSearch) {
            touch(listOf(screenCenter, SEARCH_POINT))
            needSearch = false
            return nextTime(5)
        }

        // 检测饥饿
        if (checkHunger(screen)) { // 做吃的动作需要10s
            return nextTime(10)
        }

        // 检测背包是否超重
        if (currentUnloadStatus == UnloadStatus.FINISH) {
            checkOverload(screen)
        } else {
            return doUnload()
        }

        val now = System.currentTimeMillis()

        // 调整视角
        if (now > checkSearchNextTime && now > adjustViewNextTime) {
            // Log.d(TAG, "adjust view!")
            adjustViewNextTime = System.currentTimeMillis() + ADJUST_VIEW_INTERVAL
            slide(Point(1817, 222), Point(1817, 444), 1000)
            return nextTime(1.5F)
        }

        // 搜索
        if (now > checkSearchNextTime) { // 重试10次都失败则返回战旗位置
            if (trySearchFailCount >= 10) {
                trySearchFailCount = 0
                touch(listOf(TouchAction(Point(617, 998)), TouchAction(Point(735, 675)), TouchAction(Point(940, 793)), TouchAction(Point(1141, 470), 20000),
                    TouchAction(Point(617, 998)), TouchAction(PET_POINT)))
                // Log.d(TAG, "search fail too times!")
                return nextTime(25)
            }
            // Log.d(TAG, "search -----------------------begin-----------------")
            val waitTimeSecond = search(screen)
            // Log.d(TAG, "search -----------------------end-----------------")
            checkSearchNextTime = nextTime(waitTimeSecond)
        }

        if (checkActPopup(screen)) {
            return nextTime(1)
        }

        return nextTime(0.25F)
    }

    private fun checkCanDo(screen: Bitmap): Boolean {
        if (System.currentTimeMillis() < checkCanDoNextTime) {
            return false
        }
        checkCanDoNextTime = System.currentTimeMillis() + CHECK_CAN_DO_INTERVAL
        Engine.tess.setVariable(TessBaseAPI.VAR_CHAR_WHITELIST, context.resources.getString(R.string.actions))
        Engine.tess.setImage(Bitmap.createBitmap(screen, DIG_DETECT_RECT.x, DIG_DETECT_RECT.y, DIG_DETECT_RECT.width, DIG_DETECT_RECT.height))
        val utf8Text = Engine.tess.utF8Text ?: return false
        val text = utf8Text.replace(" ", "")
        if (!actions.contains(text)) {
            return false
        }
        if (text == context.resources.getString(R.string.shiqu)) { // 拾取的话需要特殊处理
            touch(listOf(DIG_POINT, Point(1644, 613)))
        } else {
            touch(DIG_POINT)
        }
        return true
    }

    private fun checkActPopup(screen: Bitmap): Boolean {
        if (System.currentTimeMillis() < checkActPopupNextTime) {
            return false
        }
        checkActPopupNextTime = System.currentTimeMillis() + CHECK_ACT_POPUP_INTERVAL
        val checkImg = Mat()
        Utils.bitmapToMat(Bitmap.createBitmap(screen, AREA_CLOSE_ACT.x, AREA_CLOSE_ACT.y, AREA_CLOSE_ACT.width, AREA_CLOSE_ACT.height), checkImg)
        checkImg.convertTo(checkImg, CvType.CV_32F)
        val psnr = Core.PSNR(imgAct, checkImg)
        if (psnr < 20) {
            return false
        }
        touch(CLOSE_ACT_ACTION)
        return true
    }

    /**
     * 执行搜索命令
     * @return x秒后再次搜索时间
     */
    private fun search(screen: Bitmap): Float {
        val objects = ArrayList<Object>()
        if (Engine.ncnn.detect(Bitmap.createBitmap(screen, DETECT_WINDOW.x, DETECT_WINDOW.y, DETECT_WINDOW.width, DETECT_WINDOW.height), objects) != 0) {
            // Log.d(TAG, "detect fail!")
            return 1F
        }

        // Log.d(TAG, "search count:${objects.size}")

        if (objects.isEmpty()) {
            if (trySearchFailCount % 2 == 0) {
                touch(SEARCH_POINT)
            } else {
                slide(Point(1700, 920), Point(1700 + Random.nextInt(100, 200), 930), 1000)
            }
            trySearchFailCount++
            return 5F
        }

        if (objects.size in 1..3 && isOpenDebug) {
            imageQueue.put(screen)
        }

        if (objects.size < 3 && tryRotateScreen < 2) {
            slide(Point(1700, 920), Point(1700 + Random.nextInt(100, 200), 930), 1000)
            tryRotateScreen++
            return 3F
        }

        var minDistance = Int.MAX_VALUE
        var targetPoint = Point()
        Engine.tess.setVariable(TessBaseAPI.VAR_CHAR_WHITELIST, "0123456789")

        var petRect = Rect()
        val dzRect = ArrayList<Rect>()
        for (obj in objects) {
            val rect = obj.rect
            rect.x += DETECT_WINDOW.x
            rect.y += DETECT_WINDOW.y
            if (obj.label == Object.PET) {
                petRect = rect
            } else {
                if (INVALID_VIEW_WINDOW.contains(rect.tl())) {
                    // Log.d(TAG, "need adjust view!")
                    adjustViewNextTime = 0
                    return 1F
                }
                dzRect.add(rect)
            }
        }

        for (rect in dzRect) {
            val textRect = Rect(rect.x - 70, rect.y - 2, 70, rect.height + 4)
            val textImage = Bitmap.createBitmap(screen, textRect.x, textRect.y, textRect.width, textRect.height)
            val originTextImage = Bitmap.createBitmap(textImage)
            val opencvTextImg = Mat()
            Utils.bitmapToMat(textImage, opencvTextImg)
            Imgproc.cvtColor(opencvTextImg, opencvTextImg, Imgproc.COLOR_RGB2GRAY)
            Imgproc.threshold(opencvTextImg, opencvTextImg, 220.0, 255.0, Imgproc.THRESH_BINARY)
            Utils.matToBitmap(opencvTextImg, textImage)
            Engine.tess.setImage(textImage)
            var text = Engine.tess.utF8Text
            if (text.isEmpty()) {
                // Log.d(TAG, "no text! point:${rect.tl()}")
                val contours = mutableListOf<MatOfPoint>()
                val hierarchy = Mat()
                Imgproc.findContours(opencvTextImg, contours, hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE)
                for (contour in contours) {
                    val boundRect = Imgproc.boundingRect(contour)
                    if (rect.height < 20) {
                        continue
                    }
                    if (boundRect.x > 5) {
                        boundRect.x -= 5
                    } else {
                        boundRect.x = 0
                    }
                    Engine.tess.setImage(Bitmap.createBitmap(originTextImage, boundRect.x, 0, textImage.width - boundRect.x - 5, textImage.height))
                    text = Engine.tess.utF8Text
                    // Log.d(TAG, "no text! try:$text")
                    if (text.isNotEmpty()) {
                        break
                    }
                }
                if (text.isEmpty()) {
                    if (isOpenDebug) {
                        imageQueue.put(originTextImage)
                        imageQueue.put(textImage)
                    }
                    continue
                }
            }
            try {
                val distance = realDistance(text)
                // Log.d(TAG, "point:${rect.tl()} text:$text distance:$distance")
                if (distance < minDistance) {
                    val p = Point(rect.x - 40 - 10 * text.length, rect.y - 10)
                    if (petRect.empty()) {
                        targetPoint = p
                        minDistance = distance
                    } else {
                        if (p.x in petRect.x - 120..petRect.x + 120 && p.y in petRect.y - 100..petRect.y + 100) {
                            // Log.d(TAG, "ignore pet. ${petRect.tl()}")
                        } else {
                            targetPoint = p
                            minDistance = distance
                        }
                    }
                }
            } catch (e: Exception) {
                // Log.d(TAG, "detect2 error. text:[$text] ${e}")
            }
        }
        if (targetPoint.x == 0 || targetPoint.y == 0 || minDistance == 0 || minDistance == Int.MAX_VALUE) {
            // Log.d(TAG, " not found any target! $trySearchFailCount")
            if (trySearchFailCount % 2 == 0) {
                touch(SEARCH_POINT)
            } else {
                slide(Point(1700, 920), Point(1700 + Random.nextInt(100, 350), 930), 1000)
            }
            trySearchFailCount++
            return 5F
        }
        tryRotateScreen = 0
        needJump = minDistance == preDistance
        if (needJump) {
            trySearchFailCount++
        } else {
            trySearchFailCount = 0
        }
        preDistance = minDistance

        // 获取前进的点
        val screenDistance = sqrt((targetPoint.x - screenCenter.x).toDouble().pow(2.0) + (targetPoint.y - screenCenter.y).toDouble().pow(2.0))
        val touchPoint = Point(screenCenter.x + ((targetPoint.x - screenCenter.x).toDouble() * 300.0 / screenDistance).toInt(),
            screenCenter.y + ((targetPoint.y - screenCenter.y).toDouble() * 300.0 / screenDistance).toInt())

        if (needJump) {
            touch(JUMP_POINT)
        }
        if (minDistance > 5) {
            if (minDistance >= 18 && needFastMove) {
                slides(listOf(TouchAction(screenCenter, touchPoint, 1000), TouchAction(touchPoint, Point(touchPoint.x + 1, touchPoint.y + 1), 4 * 1000)),
                    listOf(TouchAction(FAST_MOVE_POINT, 0, 4500)))
            } else {
                slides(listOf(TouchAction(screenCenter, touchPoint, 1000), TouchAction(touchPoint, Point(touchPoint.x + 1, touchPoint.y + 1), 4 * 1000)))
            }
            return 5F
        } else {
            slide(screenCenter, touchPoint, 1500)
            return 1F
        }
    }

    private fun realDistance(text: String): Int {
        val lastSpace = text.lastIndexOf(' ')
        return if (lastSpace < 1) {
            text.trim().toInt()
        } else {
            text.substring(0, lastSpace).trim().toInt()
        }
    }

    /**
     * 检测饥饿
     * @return 如果已经饥饿了，则返回true吃食物
     */
    private fun checkHunger(screen: Bitmap): Boolean {
        if (System.currentTimeMillis() < checkHungerNextTime) {
            return false
        }
        checkHungerNextTime = System.currentTimeMillis() + CHECK_HUNGER_INTERVAL
        val checkImg = Mat()
        Utils.bitmapToMat(Bitmap.createBitmap(screen, AREA_HUNGER.x, AREA_HUNGER.y, AREA_HUNGER.width, AREA_HUNGER.height), checkImg)
        checkImg.convertTo(checkImg, CvType.CV_32F)
        val psnr = Core.PSNR(imgHunger, checkImg)
        // Log.d(TAG, "checkHunger psnr:$psnr")
        if (psnr < 20) {
            return false
        }
        touch(EAT_FOOD_ACTION, 500)
        return true
    }

    private fun checkOverload(screen: Bitmap) {
        if (System.currentTimeMillis() < checkOverloadNextTime) {
            return
        }
        checkOverloadNextTime = System.currentTimeMillis() + CHECK_OVERLOAD_INTERVAL
        val checkImg = Mat()
        Utils.bitmapToMat(Bitmap.createBitmap(screen, AREA_OVERLOAD.x, AREA_OVERLOAD.y, AREA_OVERLOAD.width, AREA_OVERLOAD.height), checkImg)
        val binImg = Mat()
        Imgproc.threshold(checkImg, binImg, 130.0, 255.0, Imgproc.THRESH_BINARY)
        val psnr = Core.PSNR(imgOverload, binImg)
        // Log.d(TAG, "checkOverload psnr:$psnr")
        if (psnr > 15) {
            currentUnloadStatus = UnloadStatus.BEGIN
        }
    }

    /**
     * 回家做卸载物品的动作
     */
    private fun doUnload(): Long {
        when (currentUnloadStatus) {
            UnloadStatus.BEGIN -> {
                currentUnloadStatus = UnloadStatus.GO_HOME
            }

            UnloadStatus.GO_HOME -> {
                touch(GO_HOME_ACTION, 1000)
                currentUnloadStatus = UnloadStatus.OPEN_STORE
                return nextTime(30)
            }

            UnloadStatus.OPEN_STORE -> {
                touch(OPEN_STORE_ACTION, 1000)
                currentUnloadStatus = UnloadStatus.UNLOAD
                return nextTime(6)
            }

            UnloadStatus.UNLOAD -> {
                touch(UNLOAD_ACTION)
                currentUnloadStatus = UnloadStatus.GO_BACK
                return nextTime(15)
            }

            UnloadStatus.GO_BACK -> {
                touch(GO_BACK_ACTION, 1000)
                currentUnloadStatus = UnloadStatus.CALL_PET
                return nextTime(15)
            }

            UnloadStatus.CALL_PET -> {
                touch(CALL_PET_ACTION, 1000)
                currentUnloadStatus = UnloadStatus.FINISH
                return nextTime(5)
            }

            UnloadStatus.FINISH -> {
            }
        }
        return nextTime(1)
    }

    override fun finishTask() {
        // Log.d(TAG, "finishTask")
        needJump = false
        preDistance = 0
        tryRotateScreen = 0
        currentUnloadStatus = UnloadStatus.FINISH
        trySearchFailCount = 0
        checkHungerNextTime = 0L
        checkOverloadNextTime = 0L
        checkCanDoNextTime = 0L
        checkSearchNextTime = 0L
        adjustViewNextTime = 0L
        checkActPopupNextTime = 0L
        needSearch = false
        setIsRunning(false)
    }

    private val imageQueue = SynchronousQueue<Bitmap>()

    private val UDPClient = object : Thread() {
        override fun run() {
            // Log.d(TAG, "UDPClient thread starting!")
            val socket = DatagramSocket()
            val MAX_SEND_SIZE = 1460
            val END = "1234".encodeToByteArray()
            while (true) {
                val image = imageQueue.take()
                val byteBuffer = ByteArrayOutputStream()
                image.compress(Bitmap.CompressFormat.JPEG, 100, byteBuffer)
                val bytes = byteBuffer.toByteArray()
                // Log.d(TAG, "UDPClient thread send one! ${bytes.size}")
                var offset = 0
                var len = MAX_SEND_SIZE
                while (true) {
                    if (bytes.size - offset < MAX_SEND_SIZE) {
                        len = bytes.size - offset
                    }
                    val data = DatagramPacket(bytes, offset, len, InetAddress.getByName("192.168.3.220"), 8888)
                    socket.send(data)
                    offset += MAX_SEND_SIZE
                    if (len < MAX_SEND_SIZE) {
                        socket.send(DatagramPacket(END, END.size, InetAddress.getByName("192.168.3.220"), 8888))
                        break
                    }
                }
            }
            // Log.d(TAG, "UDPClient thread finish!")
        }
    }


    enum class UnloadStatus {
        BEGIN, GO_HOME, OPEN_STORE, UNLOAD, GO_BACK, CALL_PET, FINISH
    }

    companion object {
        const val TAG = "TaskResource"
        private val SEARCH_POINT = Point(1841, 972)
        private val DIG_DETECT_RECT = Rect(1344, 580, 77, 47)
        private val DIG_POINT = Point(1382, 603)
        private val JUMP_POINT = Point(2172, 828)
        private val AREA_HUNGER = Rect(203, 58, 40, 40)
        private val FAST_MOVE_POINT = Point(2170, 660)

        private val DETECT_WINDOW = Rect(310, 120, 1680, 820)
        // 检测到目标处于这个区域时表示角度不对需要调整
        private val INVALID_VIEW_WINDOW = Rect(620, 250, 1170, 560)

        // 吃食物
        private val EAT_FOOD_ACTION = listOf(Point(2086, 222), Point(1778, 191), Point(2038, 280), Point(1769, 393))
        private val AREA_OVERLOAD = Rect(994, 979, 40, 40)

        // 回家
        private val GO_HOME_ACTION = listOf(Point(617, 998), Point(735, 675), Point(870, 326), Point(1084, 310))

        // 回到战旗位置
        private val GO_BACK_ACTION = listOf(Point(617, 998), Point(735, 675), Point(940, 793), Point(1141, 470))

        // 打开仓库
        private val OPEN_STORE_ACTION = listOf(Point(1540, 530), Point(1384, 440), Point(1519, 190), Point(1373, 604))

        // 保存：多选 -> 选择 -> 存入 -> 快捷存入 -> 确定 -> 关闭
        private val UNLOAD_ACTION =
            listOf(TouchAction(Point(1563, 960), 1000), TouchAction(Point(1519, 190), 1000), TouchAction(Point(1833, 963), 1000), TouchAction(Point(2106, 970), 1000),
                TouchAction(Point(1343, 711), 5000), TouchAction(Point(2087, 46)))

        // 宠物坐标
        private val PET_POINT = Point(1255, 320)
        // 呼唤宠物
        private val CALL_PET_ACTION = listOf(Point(617, 998), PET_POINT, SEARCH_POINT)

        // 活动界面关闭按钮
        private val AREA_CLOSE_ACT = Rect(2200, 64, 43, 43)
        private val CLOSE_ACT_ACTION = Point(2220, 80)


        // 检测饥饿时间间隔(毫秒)
        private const val CHECK_HUNGER_INTERVAL = 10 * 60 * 1000

        // 检测超重时间间隔(毫秒)
        private const val CHECK_OVERLOAD_INTERVAL = 5 * 60 * 1000

        // 检测可以点击动作时间间隔(毫秒)
        private const val CHECK_CAN_DO_INTERVAL = 250

        // 调整视角时间间隔(毫秒)
        private const val ADJUST_VIEW_INTERVAL = 60 * 1000

        // 检测活动弹窗时间间隔(毫秒)
        private const val CHECK_ACT_POPUP_INTERVAL = 60 * 1000
    }
}